#pragma once

#include <malloc.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <assert.h>
#include <stdint.h>
#include <ctype.h>

// TODO: strtomax (vagy valami hasonlo) is letezik. vagy nem? 

#ifdef _MSC_VER
#define strtoll     _strtoi64
#define strtoull    _strtoui64
#endif


const uint32_t CSTR_MAGIC_NUMBER = ('C' | ('S' << 8) | ('T' << 16) | ('R' << 24));


#ifdef _MSC_VER
  #ifndef NOINLINE
    #define NOINLINE __declspec(noinline)
  #endif
#else
  #define NOINLINE
#endif


template <int BUFFSIZE> class String;

#define SHORTSTRING_LEN     16
#define STRING_LEN          80


typedef String<SHORTSTRING_LEN> CShortString;       // 32 byte (x64)
typedef String<STRING_LEN>      CChString;          // 96 byte (x64)


//
// class String<int BUFFSIZE>
//
// BUFFSIZE: a belso buffer merete (ha nem eleg, akkor a malloc() -al foglal helyet)
//
// ! a c_str() es buff() fuggvenyek: csak a kovetkezo nem const fuggvenyig biztonsagos a visszaadaott mutato!
//

template <int BUFFSIZE>
class String
{
public:

    static const int npos = 0x7fffffff;

    String()                                    { ctor_reset(); }
    String(const char *str)                     { ctor_reset(); Cat(str); }
    String(const String &str)                   { ctor_reset(); Cat(str); }
    ~String();

    void Assign(const char *str);
    void Assign(const String &str);
    void AssignExact(const char *str);          // memoria foglalas pont akkora, amekkora kell
    void MoveTo(String *target);
    void Steal(String *from)                    { from->MoveTo(this); }

    void Clear();

    size_t GetCapacity() const                  { return m_Capacity; }
    size_t GetLength() const                    { return m_Length; }
    size_t length() const                       { return m_Length; }
    int    ilength() const                      { return (int)m_Length; }

    const char *c_str() const                   { return buff(); }

    void Minimize();

    String *TrimLeft();
    String *TrimRight();
    String *Trim()                              { TrimRight(); return TrimLeft(); }

    bool IsEmpty() const                        { return m_Length == 0; }
    bool IsSpace() const;
    bool StartsWith(const char *pattern) const;
    bool EndsWith(const char *pattern) const;
    int  GetLast() const                        { return m_Length ? (uint8_t)buff()[m_Length - 1] : 0; }

    void SetLength(int size, int ch_if_extend);
    void Extend(int ch, int size);              // kiterjeszti size hosszusagura, a veget ch -kal tolti fel

    bool LoadBin(FILE *in);
    bool SaveBin(FILE *out) const;

    void CatN(const char *str, size_t size);    // str legalabb size hosszu
    void CatNSafe(const char *str, size_t size);// megnezi, van-e str legalabb size hosszu
    void Cat(const String &str)                 { CatN(str.c_str(), str.m_Length); }
    void Cat(const char *str)                   { CatN(str, strlen(str)); }
    void CatCh(char ch)                         { CatN(&ch, 1); }

    int ToInt(bool *pResultIsValid) const;
    unsigned int ToUInt(bool *pResultIsValid) const;
    int64_t ToInt64(bool *pResultIsValid) const;
    uint64_t ToUInt64(bool *pResultIsValid) const;
    float ToFloat(bool *pResultIsValid) const;
    double ToDouble(bool *pResultIsValid) const;

    char &at(size_t i)                          { assert(i < m_Length); return buff()[i]; }
    char &operator[] (size_t i)                 { assert(i < m_Length); return buff()[i]; }
    const char &at(size_t i) const              { assert(i < m_Length); return buff()[i]; }
    const char &operator[] (size_t i) const     { assert(i < m_Length); return buff()[i]; }

    // =
    String &operator= (const String &str)          { if (&str != this) { Assign(str); } return *this; }
    String &operator= (const char *str)            { Assign(str); return *this; }

    // +=
    String &operator+= (const String &str)      { CatN(str.buff(), str.m_Length); return *this; }
    String &operator+= (const char *str)        { Cat(str); return *this; }
    String &operator+= (char ch)                { CatCh(ch); return *this; }

    // <
    bool operator< (const String &str) const    { return strcmp(buff(), str.buff()) < 0; }

    // reszsztringek kepzese

    String *CopySubString(String *dst, int first, int last) const;      // negativ szam: a vegerol szamolja (-1 az utolso char)
    String *CopySubStringN(String *dst, int first, size_t n) const;     // first: negativ is lehet

    void Remove(int first, int last);
    void RemoveN(int first, size_t n);
    void RemoveLast()                           { Remove((int)m_Length-1, (int)m_Length-1); }

    void Insert(int ch, int pos);
    void InsertStr(const char *str, int pos);

    char *buff()                                { return IsDynamic() ? m_pDynamic : m_aStatic; }
    const char *buff() const                    { return IsDynamic() ? m_pDynamic : m_aStatic; }

    int GetByteSize()                           { return (int)m_Length + 1; }

private:

    void  ctor_reset();
    bool  IsDynamic() const                     { return m_Capacity > BUFFSIZE; }

    char *MakeRoom(size_t size);                // valt a static es dynamic buffer kozott, helyet foglal

    char    m_aStatic[BUFFSIZE];
    char   *m_pDynamic;
    size_t  m_Length;
    size_t  m_Capacity;
};


template <int BUFFSIZE>
void String<BUFFSIZE>::ctor_reset()
{
    m_pDynamic = NULL;
    m_aStatic[0] = '\0';
    m_Length = 0;
    m_Capacity = BUFFSIZE;
}


template <int BUFFSIZE>
String<BUFFSIZE>::~String()
{
    if (m_pDynamic)
        free(m_pDynamic);
}


template <int BUFFSIZE>
char *String<BUFFSIZE>::MakeRoom(size_t size)
{
    if (size > BUFFSIZE && size > m_Capacity)
    {
        //while ((m_Capacity <<= 1) < size)
        //    ;

        //while (m_Capacity < size)
        //    m_Capacity += m_Capacity >> 1;

        while (m_Capacity < size)
            m_Capacity += (m_Capacity / 3) + 16;

        m_pDynamic = (char*)realloc(m_pDynamic, m_Capacity);
    }

    return buff();
}


template <int BUFFSIZE>
void String<BUFFSIZE>::Assign(const char *str)
{
    Clear();
    Cat(str);
}


template <int BUFFSIZE>
void String<BUFFSIZE>::Assign(const String &str)
{
    if (&str != this)
    {
        Clear();
        CatN(str.buff(), str.m_Length);
    }
}


template <int BUFFSIZE>
void String<BUFFSIZE>::AssignExact(const char *str)
{
    Clear();

    size_t len = strlen(str);

    if (len < BUFFSIZE)
    {
        if (IsDynamic())
        {
            free(m_pDynamic);
            m_pDynamic = NULL;
        }

        m_Capacity = BUFFSIZE;
        memcpy(m_aStatic, str, len + 1);
    }
    else
    {
        m_Capacity = len + 1;
        m_pDynamic = (char*)realloc(m_pDynamic, m_Capacity);
        memcpy(m_pDynamic, str, len + 1);
    }

    m_Length = len;
}


template <int BUFFSIZE>
void String<BUFFSIZE>::Clear()
{
    *buff() = '\0';
    m_Length = 0;
}


template <int BUFFSIZE>
void String<BUFFSIZE>::CatNSafe(const char *str, size_t size)
{
    size_t l = strlen(str);

    CatN(str, l < size ? l : size);
}


template <int BUFFSIZE>
void String<BUFFSIZE>::CatN(const char *str, size_t size)
{
    if (size == 0 || str[0] == '\0')
        return ;

    size_t newsize = m_Length + 1 + size;

    if (newsize <= m_Capacity)
    {
        memcpy(buff() + m_Length, str, size);
    }
    else
    {
        bool dynamic_old = IsDynamic();

        MakeRoom(newsize);

        if (dynamic_old == false && IsDynamic())
            memcpy(m_pDynamic, m_aStatic, m_Length);

        memcpy(buff() + m_Length, str, size);
    }

    m_Length = newsize - 1;
    buff()[m_Length] = '\0';
}


template <int BUFFSIZE>
void String<BUFFSIZE>::Minimize()
{
    if (m_Length < BUFFSIZE)
    {
        if (IsDynamic())
        {
            free(m_pDynamic);
            m_pDynamic = NULL;
        }
        m_Capacity = BUFFSIZE;
    }
    else if (m_Capacity != m_Length + 1)
    {
        m_Capacity = m_Length + 1;
        m_pDynamic = (char*)realloc(m_pDynamic, m_Capacity);
    }
}


template <int BUFFSIZE>
bool String<BUFFSIZE>::IsSpace() const
{
    const char *s = c_str();

    for (size_t i=0; i<m_Length; ++i)
    {
        if (!isspace((uint8_t)s[i]))
            return false;
    }

    return m_Length != 0;
}


template <int BUFFSIZE>
bool String<BUFFSIZE>::StartsWith(const char *start) const
{
    if (start[0] == '\0')
        return false;

    const char *s = c_str();
    size_t i;

    for (i=0; i<m_Length && start[i]; ++i)
    {
        if (s[i] != start[i])
            return false;
    }

    return start[i] == 0;
}


template <int BUFFSIZE>
bool String<BUFFSIZE>::EndsWith(const char *start) const
{
    size_t pl = strlen(start);

    if (pl > m_Length || pl == 0)
        return false;

    return strcmp(&c_str()[m_Length - pl], start) == 0;
}


template <int BUFFSIZE>
String<BUFFSIZE> operator+ (const String<BUFFSIZE> &s1, const String<BUFFSIZE> &s2)
{
    String<BUFFSIZE> tmp(s1);
    tmp.Cat(s2);
    return tmp;
}


template <int BUFFSIZE>
String<BUFFSIZE> operator+ (const char *s1, const String<BUFFSIZE> &s2)
{
    String<BUFFSIZE> tmp(s1);
    tmp.Cat(s2);
    return tmp;
}


template <int BUFFSIZE>
String<BUFFSIZE> operator+ (const String<BUFFSIZE> &s1, const char *s2)
{
    String<BUFFSIZE> tmp(s1);
    tmp.Cat(s2);
    return tmp;
}


template <int BUFFSIZE>
bool operator== (const char *s1, const String<BUFFSIZE> &s2)
{
    return strcmp(s1, s2.c_str()) == 0;
}


template <int BUFFSIZE>
bool operator== (const String<BUFFSIZE> &s1, const String<BUFFSIZE> &s2)
{
    return strcmp(s1.c_str(), s2.c_str()) == 0;
}


template <int BUFFSIZE>
bool operator== (const String<BUFFSIZE> &s1, const char *s2)
{
    return strcmp(s1.c_str(), s2) == 0;
}


template <int BUFFSIZE>
bool operator!= (const char *s1, const String<BUFFSIZE> &s2)
{
    return strcmp(s1, s2.c_str()) != 0;
}


template <int BUFFSIZE>
bool operator!= (const String<BUFFSIZE> &s1, const String<BUFFSIZE> &s2)
{
    return strcmp(s1.c_str(), s2.c_str()) != 0;
}


template <int BUFFSIZE>
bool operator!= (const String<BUFFSIZE> &s1, const char *s2)
{
    return strcmp(s1.c_str(), s2) != 0;
}


template <int BUFFSIZE>
String<BUFFSIZE> *String<BUFFSIZE>::CopySubString(String *dst, int first, int last) const
{
    dst->Clear();

    int len = (int)m_Length;

    if (last == npos)
        last = (int)m_Length - 1;

    if (first < 0)
        first = len + first;        // len - (-first);

    if (first >= len || (last < 0 && last < -((int)m_Length)))
        return dst;

    if (last < 0)
        last = len + last;          // len - (-last);

    if (last >= len)
        last = len - 1;

    if (first < 0 || (last - first) < 0)
        return dst;

    dst->CatN(buff() + first, last - first + 1);

    return dst;
}


template <int BUFFSIZE>
String<BUFFSIZE> *String<BUFFSIZE>::CopySubStringN(String *dst, int first, size_t n) const
{
    dst->Clear();

    int len = (int)m_Length;


    if (n == npos)
        n = m_Length;

    if (first < 0)
        first = len + first;        // len - (-first);

    if (first >= len)
        return dst;

    if (first < 0)
        return dst;

    if ((unsigned)first + n > m_Length)
        n = len - first;

    dst->CatN(buff() + first, n);

    return dst;
}


template <int BUFFSIZE>
void String<BUFFSIZE>::Remove(int first, int last)
{
    int len = (int)m_Length;

    if (last == npos)
        last = (int)m_Length - 1;

    if (first < 0)
        first = len + first;        // len - (-first);

    if (first >= len || (last < 0 && last < -((int)m_Length)))
        return ;

    if (last < 0)
        last = len + last;          // len - (-last);

    if (last >= len)
        last = len - 1;

    if (first < 0 || (last - first) < 0)
        return ;

    memmove(buff() + first, buff() + last + 1, m_Length - last + 1);

    m_Length -= last - first + 1;
}


template <int BUFFSIZE>
void String<BUFFSIZE>::RemoveN(int first, size_t n)
{
    if (n)
    {
        if (first < 0)
            first = (int)m_Length + first;        // len - (-first);

        Remove(first, first + ((int)n) - 1);
    }
}

template <int BUFFSIZE>
void String<BUFFSIZE>::Insert(int ch, int pos)
{
    CatCh((char)ch);

    int len = (int)m_Length;

    if (pos < 0)
        pos += len;

    if (pos < 0)
        pos = 0;

    if (pos < len)
    {
        memmove(buff() + pos + 1, buff() + pos, len - pos - 1);
        buff()[pos] = (char)ch;
    }
}


template <int BUFFSIZE>
void String<BUFFSIZE>::InsertStr(const char *str, int pos)
{
    size_t len = strlen(str);

    int mylen = (int)m_Length;

    if (pos < 0)
        pos += mylen;

    if (pos < 0)
        pos = 0;

    if (len)
    {
        MakeRoom(m_Length + len);
        m_Length += len;

        if (pos > mylen)
            pos = mylen;

        memmove(buff() + pos + len, buff() + pos, m_Length - pos - len);
        memcpy(&buff()[pos], str, len);
    }
}


template <int BUFFSIZE>
void String<BUFFSIZE>::MoveTo(String *target)
{
    if (target == this)
        return ;

    target->Clear();

    if (target->m_pDynamic)
        free(target->m_pDynamic);

    target->m_Length = m_Length;
    target->m_Capacity = m_Capacity;
    target->m_pDynamic = m_pDynamic;

    if (IsDynamic() == false)
    {
        memcpy(target->m_aStatic, m_aStatic, m_Length + 1);
    }

    m_Capacity = BUFFSIZE;
    m_Length = 0;
    m_pDynamic = NULL;
    m_aStatic[0] = '\0';
}


template <int BUFFSIZE>
void swap(String<BUFFSIZE> &s1, String<BUFFSIZE> &s2)
{
    String<BUFFSIZE> tmp;

    tmp.Steal(&s1);
    s1.Steal(&s2);
    s2.Steal(&tmp);
}


template <int BUFFSIZE>
bool String<BUFFSIZE>::LoadBin(FILE *in)
{
    Clear();

    uint32_t magic;

    if (fread(&magic, sizeof(magic), 1, in) != 1 || magic != CSTR_MAGIC_NUMBER)
        return false;

    size_t size;

    if (fread(&size, sizeof(size), 1, in) != 1)
        return false;

    MakeRoom(size);
    m_Length = size - 1;

    if (fread(buff(), size, 1, in) != 1)
    {
        Clear();
        return false;
    }

    return true;
}


template <int BUFFSIZE>
bool String<BUFFSIZE>::SaveBin(FILE *out) const
{
    size_t size = m_Length + 1;

    return (fwrite(&CSTR_MAGIC_NUMBER, sizeof(CSTR_MAGIC_NUMBER), 1, out) == 1 &&
            fwrite(&size, sizeof(size), 1, out) == 1 &&
            fwrite(buff(), size, 1, out) == 1);
}


template <int BUFFSIZE>
int String<BUFFSIZE>::ToInt(bool *pResultIsValid) const
{
    errno = 0;

    const char *start = c_str();
    char *end;
    int ret = (int)strtol(start, &end, 0);

    if (pResultIsValid)
        *pResultIsValid = errno != ERANGE && start != end;

    return ret;
}


template <int BUFFSIZE>
unsigned  String<BUFFSIZE>::ToUInt(bool *pResultIsValid) const
{
    errno = 0;

    const char *start = c_str();
    char *end;
    unsigned  ret = (unsigned )strtoul(start, &end, 0);

    if (pResultIsValid)
        *pResultIsValid = errno != ERANGE && start != end;

    return ret;
}


template <int BUFFSIZE>
int64_t String<BUFFSIZE>::ToInt64(bool *pResultIsValid) const
{
    errno = 0;

    const char *start = c_str();
    char *end;
    int64_t ret = strtoll(start, &end, 0);

    if (pResultIsValid)
        *pResultIsValid = errno != ERANGE && start != end;

    return ret;
}


template <int BUFFSIZE>
uint64_t String<BUFFSIZE>::ToUInt64(bool *pResultIsValid) const
{
    errno = 0;

    const char *start = c_str();
    char *end;
    uint64_t ret = strtoull(start, &end, 0);

    if (pResultIsValid)
        *pResultIsValid = errno != ERANGE && start != end;

    return ret;
}


template <int BUFFSIZE>
float String<BUFFSIZE>::ToFloat(bool *pResultIsValid) const
{
    return (float)ToDouble(pResultIsValid);
}


template <int BUFFSIZE>
double String<BUFFSIZE>::ToDouble(bool *pResultIsValid) const
{
    errno = 0;

    const char *start = c_str();
    char *end;
    double ret = strtod(start, &end);

    if (pResultIsValid)
        *pResultIsValid = errno != ERANGE && start != end;

    return ret;
}


template <int BUFFSIZE>
void String<BUFFSIZE>::Extend(int ch, int size)
{
    while (ilength() < size)
        CatCh(ch);
}


template <int BUFFSIZE>
void String<BUFFSIZE>::SetLength(int size, int ch_if_extend)
{
    if (size > (int)m_Length)
    {
        Extend(ch_if_extend, size);
    }
    else if (size < (int)m_Length)
    {
        if (size < 0)
            size = 0;

        m_Length = size;
        buff()[m_Length] = '\0';
    }
}


template <int BUFFSIZE>
String<BUFFSIZE> *String<BUFFSIZE>::TrimLeft()
{
    for (size_t i=0; i<m_Length; ++i)
    {
        if (isspace((uint8_t)at(i)) == 0)
        {
            RemoveN(0, i);
            return this;
        }
    }

    return this;
}


template <int BUFFSIZE>
String<BUFFSIZE> *String<BUFFSIZE>::TrimRight()
{
    while (isspace((uint8_t)GetLast()))
        RemoveLast();

    return this;
}
